package baiduCaptchaV2Resolve

import (
	"bytes"
	"context"
	"encoding/base64"
	"encoding/json"
	"errors"
	"fmt"
	"gitee.com/kaylee595/gkaylee/kerr"
	"github.com/imroc/req/v3"
	"log/slog"
	url2 "net/url"
	"time"
	"yyLogin/pkg/jfbym"
	"yyLogin/pkg/jfbym/customApi"
)

const defaultBaseApi = "https://passport.baidu.com/cap/"

type Client struct {
	httpClient           *req.Client
	ak                   string
	refererForHeader     string
	refererForReqPayload string
	baseApi              *url2.URL
	ym                   *jfbym.Client
	maxAttempt           int
}

type Option func(c *Client)

func WithHttpClient(client *req.Client) Option {
	return func(c *Client) {
		c.httpClient = client
	}
}

func WithBaseApi(baseApi *url2.URL) Option {
	return func(c *Client) {
		c.baseApi = baseApi
	}
}

func WithMaxAttempt(maxAttempt int) Option {
	return func(c *Client) {
		c.maxAttempt = maxAttempt
	}
}

var _, _, _ = WithHttpClient, WithBaseApi, WithMaxAttempt

func NewClient(ak, refererForHeader, refererForReqPayload string, ym *jfbym.Client, options ...Option) *Client {
	c := &Client{
		httpClient:           req.DefaultClient(),
		ak:                   ak,
		refererForHeader:     refererForHeader,
		refererForReqPayload: refererForReqPayload,
		baseApi:              kerr.Must(url2.Parse(defaultBaseApi)),
		ym:                   ym,
	}
	for _, option := range options {
		option(c)
	}
	return c
}

type RespInit struct {
	Tk   string `json:"tk"`
	As   string `json:"as"`
	Ds   string `json:"ds"`
	Ls   string `json:"ls"`
	Conf struct {
		Feedback struct {
			Default string `json:"default"`
			Pc      string `json:"pc"`
			Wap     string `json:"wap"`
		} `json:"feedback"`
		Return string `json:"return"`
	} `json:"conf"`
}

func (c *Client) init() (*RespInit, error) {
	url := c.baseApi.JoinPath("init").String()
	payload := map[string]string{
		"_":      fmt.Sprintf("%d", time.Now().UnixMilli()),
		"refer":  c.refererForReqPayload,
		"ak":     c.ak,
		"ver":    "2",
		"scene":  "",
		"ds":     "",
		"tk":     "",
		"as":     "",
		"reinit": "0",
	}
	resp := c.httpClient.Post(url).SetFormData(payload).SetHeader("Referer", c.refererForHeader).Do()
	if resp.Err != nil {
		return nil, resp.Err
	}
	b := &RespBaseApi{}
	if err := resp.UnmarshalJson(b); err != nil {
		return nil, kerr.WrapTraceStack(err)
	}
	if b.Code != 0 {
		return nil, kerr.WrapTraceStack(b)
	}
	ret := &RespInit{}
	if err := b.UnmarshalData(ret); err != nil {
		return nil, kerr.WrapTraceStack(err)
	}
	return ret, nil
}

type ReqRefresh map[string]any

type RespStyle struct {
	Backstr     string `json:"backstr"`
	Captchalist []struct {
		Id     string `json:"id"`
		Source struct {
			Back struct {
				Path string `json:"path"`
			} `json:"back"`
		} `json:"source"`
		Type string `json:"type"`
	} `json:"captchalist"`
	Conf struct {
		Feedback struct {
			Default string `json:"default"`
			Pc      string `json:"pc"`
			Wap     string `json:"wap"`
		} `json:"feedback"`
	} `json:"conf"`
	Ext struct {
		Sup int      `json:"sup"`
		P   struct { // todo: 不同的验证码可能结构不同, 需要求证
			Q struct {
				Spin0 string `json:"spin-0"`
			} `json:"q"`
			M string `json:"m"`
			C string `json:"c"`
		} `json:"p"`
		EnConf string `json:"en_conf"`
	} `json:"ext"`
}

func (c *Client) style(tk, type_ string, refresh ReqRefresh) (*RespStyle, error) {
	url := c.baseApi.JoinPath("style").String()
	payload := map[string]string{
		"_":     fmt.Sprintf("%d", time.Now().UnixMilli()),
		"refer": c.refererForReqPayload,
		"ak":    c.ak,
		"tk":    tk,
		"scene": "",
		"ver":   "2",
		"isios": "0", // 苹果移动设备则为 1
		"type":  type_,
	}
	if refresh != nil {
		payload["refresh"] = string(kerr.Must(json.Marshal(refresh)))
	}
	resp := c.httpClient.Post(url).SetFormData(payload).SetHeader("Referer", c.refererForHeader).Do()
	if resp.Err != nil {
		return nil, resp.Err
	}
	b := &RespBaseApi{}
	if err := resp.UnmarshalJson(b); err != nil {
		return nil, kerr.WrapTraceStack(err)
	}
	if b.Code != 0 {
		return nil, kerr.WrapTraceStack(b)
	}
	ret := &RespStyle{}
	if err := b.UnmarshalData(ret); err != nil {
		return nil, kerr.WrapTraceStack(err)
	}
	return ret, nil
}

type RespLog struct {
	As  string `json:"as"`
	Ds  string `json:"ds"`
	Tk  string `json:"tk"`
	Op  int    `json:"op"`
	Opp int    `json:"opp"`
	Msg string `json:"msg"`
}

func (r *RespLog) Error() string {
	if r.Msg != "" {
		return fmt.Sprintf("op: %d, opp: %d, msg: %s", r.Op, r.Opp, r.Msg)
	}
	return fmt.Sprintf("op: %d, opp: %d", r.Op, r.Opp)
}

func (c *Client) log(tk, as, typeid, fs string) (*RespLog, error) {
	url := c.baseApi.JoinPath("log").String()
	payload := map[string]string{
		"_":      fmt.Sprintf("%d", time.Now().UnixMilli()),
		"refer":  c.refererForReqPayload,
		"ak":     c.ak,
		"as":     as,
		"scene":  "",
		"tk":     tk,
		"ver":    "2",
		"cv":     "submit",
		"typeid": typeid,
		"fs":     fs,
	}
	resp := c.httpClient.Post(url).SetFormData(payload).SetHeader("Referer", c.refererForHeader).Do()
	if resp.Err != nil {
		return nil, resp.Err
	}
	b := &RespBaseApi{}
	if err := resp.UnmarshalJson(b); err != nil {
		return nil, kerr.WrapTraceStack(err)
	}
	if b.Code != 0 {
		return nil, kerr.WrapTraceStack(b)
	}
	ret := &RespLog{}
	if err := b.UnmarshalData(ret); err != nil {
		return nil, kerr.WrapTraceStack(err)
	}
	if ret.Op != 1 {
		return nil, kerr.WrapTraceStack(ret)
	}
	return ret, nil
}

type CaptchaType string

const (
	CaptchaTypeSpin CaptchaType = "spin"
)

// Do 成功将返回 ds 值
func (c *Client) Do(type_ CaptchaType) (*RespLog, error) {
	initRet, err := c.init()
	if err != nil {
		return nil, kerr.WrapTraceStack(err)
	}
	var attemptCounter int
	var refresh ReqRefresh // 首次请求为空, 如果验证码识别失败, 需要传递参数
style:
	styleRet, err := c.style(initRet.Tk, string(type_), refresh)
	if err != nil {
		return nil, kerr.WrapTraceStack(err)
	}
	if len(styleRet.Captchalist) == 0 {
		return nil, kerr.New("captcha list is empty")
	}
	captchaInfo := styleRet.Captchalist[0]
	switch CaptchaType(captchaInfo.Type) {
	case CaptchaTypeSpin: // 旋转验证码
		imgBase64 := &bytes.Buffer{}
		resp := c.httpClient.Get(captchaInfo.Source.Back.Path).
			SetHeader("Referer", c.refererForHeader).
			SetOutput(base64.NewEncoder(base64.StdEncoding, imgBase64)).Do()
		if resp.Err != nil {
			return nil, kerr.WrapTraceStack(resp.Err)
		}
		t90009Ret := customApi.New90009(imgBase64.String())
		err = c.ym.CustomApi(t90009Ret)
		if err != nil {
			return nil, kerr.WrapTraceStack(err)
		}
		acC := float64(t90009Ret.Data) / 360
		styleP := styleRet.Ext.P
		fs, err := GetFS(context.Background(), acC, initRet.As, styleRet.Backstr, styleP.Q.Spin0, styleP.M, styleP.C)
		if err != nil {
			return nil, kerr.WrapTraceStack(err)
		}
		logRet, err := c.log(initRet.Tk, initRet.As, captchaInfo.Id, fs)
		if err != nil {
			if errors.As(err, &logRet) && logRet.Op == 3 { // 识别失败, 上报错误(退还积分)
				e := c.ym.RefundApi(t90009Ret.UniqueCode)
				if e != nil {
					err = errors.Join(err, kerr.WrapTraceStack(e))
				}
				if attemptCounter < c.maxAttempt {
					slog.Warn("云码验证码识别失败, 正在进行下一轮重试验证", "当前已经重试次数", attemptCounter, "最大重试次数", c.maxAttempt)
					if refresh == nil {
						refresh = make(ReqRefresh, 2)
					}
					refresh["capId"] = captchaInfo.Id
					refresh["backstr"] = styleRet.Backstr
					attemptCounter++
					goto style
				}
			}
			return nil, kerr.WrapTraceStack(err)
		}
		return logRet, nil
	default:
		return nil, kerr.WrapTraceStack(fmt.Errorf("unsupported captcha type: %s", captchaInfo.Type))
	}
}
